<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fabric.js双模式圈涂工具</title>
    <script src="./fabric.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f5f5f5;
        }
        .container {
            width: 100%;
            max-width: 1000px;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }
        h3 {
            color: #333;
            text-align: center;
            margin-bottom: 20px;
        }
        #status-display {
            margin-bottom: 15px;
            font-weight: bold;
            color: #555;
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 4px;
        }
        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            margin-bottom: 20px;
            padding: 10px;
            background-color: #f9f9f9;
            border-radius: 4px;
        }
        button {
            padding: 8px 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #45a049;
        }
        button.active {
            background-color: #2E7D32;
            box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
        }
        .tool-option {
            display: flex;
            align-items: center;
            gap: 5px;
            margin-right: 10px;
        }
        .tool-option label {
            font-size: 14px;
            color: #555;
        }
        .tool-option input[type="range"] {
            width: 100px;
        }
        #canvas-container {
            border: 1px solid #ccc;
            margin-bottom: 20px;
            position: relative;
            border-radius: 4px;
            overflow: hidden;
            background-color: #ffffff;
        }
        #export-container {
            margin-top: 20px;
            text-align: center;
            padding: 15px;
            background-color: #f9f9f9;
            border-radius: 4px;
        }
        #export-image {
            max-width: 100%;
            border: 1px solid #ccc;
            margin-top: 10px;
            border-radius: 4px;
            background-color: #000000;
        }
        #download-link {
            display: inline-block;
            margin-top: 10px;
            padding: 8px 16px;
            background-color: #2196F3;
            color: white;
            text-decoration: none;
            border-radius: 4px;
            transition: background-color 0.3s;
        }
        #download-link:hover {
            background-color: #0b7dda;
        }
        .info-box {
            margin-top: 20px;
            padding: 15px;
            background-color: #e8f5e9;
            border-radius: 4px;
            color: #333;
            font-size: 14px;
        }
        .info-box h4 {
            margin-top: 0;
            color: #2E7D32;
        }
    </style>
</head>
<body>
    <div class="container">
        <h3>Fabric.js双模式圈涂工具</h3>
        
        <div class="info-box">
            <h4>使用说明</h4>
            <p>1. 画笔模式：绘制不填充的线条，适合标记重点</p>
            <p>2. 圈选模式：绘制闭合区域并自动填充，适合框选区域</p>
            <p>3. 导出功能：将标记内容转换为黑白图像（线条为白色，填充区域为白色，背景为黑色）</p>
        </div>
        
        <div id="status-display">当前模式: 画笔</div>
        
        <div class="controls">
            <button id="brushBtn" class="active">画笔模式</button>
            <button id="selectionBtn">圈选模式</button>
            <button id="clearBtn">清除所有</button>
            <button id="exportBtn">导出黑白图片</button>
            
            <div class="tool-option">
                <label for="brushColor">画笔颜色:</label>
                <input type="color" id="brushColor" value="#000000">
            </div>
            
            <div class="tool-option">
                <label for="brushSize">画笔大小:</label>
                <input type="range" id="brushSize" min="10" max="50" value="20">
            </div>
            
            <div class="tool-option">
                <label for="fillColor">填充颜色:</label>
                <input type="color" id="fillColor" value="#ff0000">
            </div>
            
            <div class="tool-option">
                <label for="closureThreshold">闭合阈值:</label>
                <input type="range" id="closureThreshold" min="5" max="50" value="20">
                <span id="thresholdValue">20%</span>
            </div>
            
            <div class="tool-option">
                <label for="fillDelay">填充延迟:</label>
                <input type="range" id="fillDelay" min="500" max="3000" step="500" value="1500">
                <span id="delayValue">1.5秒</span>
            </div>
        </div>
        
        <div id="canvas-container">
            <canvas id="fabricCanvas" width="800" height="600"></canvas>
        </div>
        
        <div id="export-container">
            <h4>导出预览</h4>
            <img id="export-image" src="" alt="导出的黑白图片">
            <a id="download-link" download="highlight_result.png" style="display: none;">下载图片</a>
        </div>
    </div>
    
    <script>
        // 初始化画布
        const canvas = new fabric.Canvas('fabricCanvas');
        
        // 状态变量
        let currentMode = 'brush'; // 'brush' 或 'selection'
        let brushColor = '#000000';
        let brushSize = 20;
        let fillColor = '#ff0000';
        let closureThreshold = 20; // 闭合阈值百分比
        let fillDelay = 1500; // 填充延迟(毫秒)
        let fillTimer = null;
        let isMouseMoving = false;
        let isDrawing = false;
        let currentPath = null;
        
        // 获取DOM元素
        const brushBtn = document.getElementById('brushBtn');
        const selectionBtn = document.getElementById('selectionBtn');
        const clearBtn = document.getElementById('clearBtn');
        const exportBtn = document.getElementById('exportBtn');
        const brushColorInput = document.getElementById('brushColor');
        const brushSizeInput = document.getElementById('brushSize');
        const fillColorInput = document.getElementById('fillColor');
        const closureThresholdInput = document.getElementById('closureThreshold');
        const thresholdValueSpan = document.getElementById('thresholdValue');
        const fillDelayInput = document.getElementById('fillDelay');
        const delayValueSpan = document.getElementById('delayValue');
        const statusDisplay = document.getElementById('status-display');
        const exportImage = document.getElementById('export-image');
        const downloadLink = document.getElementById('download-link');
        
        // 设置画笔颜色
        brushColorInput.addEventListener('input', function() {
            brushColor = this.value;
            if (currentMode === 'brush') {
                canvas.freeDrawingBrush.color = brushColor;
            } else {
                canvas.freeDrawingBrush.color = '#ff0000'; // 圈选模式使用不同颜色区分
            }
        });
        
        // 设置画笔大小
        brushSizeInput.addEventListener('input', function() {
            brushSize = parseInt(this.value);
            canvas.freeDrawingBrush.width = brushSize;
        });
        
        // 设置填充颜色
        fillColorInput.addEventListener('input', function() {
            fillColor = this.value;
        });
        
        // 设置闭合阈值
        closureThresholdInput.addEventListener('input', function() {
            closureThreshold = parseInt(this.value);
            thresholdValueSpan.textContent = `${closureThreshold}%`;
        });
        
        // 设置填充延迟
        fillDelayInput.addEventListener('input', function() {
            fillDelay = parseInt(this.value);
            delayValueSpan.textContent = `${fillDelay/1000}秒`;
        });
        
        // 初始化画笔设置
        canvas.freeDrawingBrush.color = brushColor;
        canvas.freeDrawingBrush.width = brushSize;
        canvas.isDrawingMode = true; // 默认启用画笔模式
        
        // 切换到画笔模式
        brushBtn.addEventListener('click', function() {
            switchMode('brush');
        });
        
        // 切换到圈选模式
        selectionBtn.addEventListener('click', function() {
            switchMode('selection');
        });
        
        // 清除所有按钮事件
        clearBtn.addEventListener('click', function() {
            canvas.clear();
            exportImage.src = '';
            downloadLink.style.display = 'none';
            if (fillTimer) clearTimeout(fillTimer);
            isDrawing = false;
            currentPath = null;
        });
        
        // 导出黑白图片按钮事件
        exportBtn.addEventListener('click', function() {
            exportBlackAndWhiteImage();
        });
        
        // 模式切换函数
        function switchMode(mode) {
            currentMode = mode;
            
            // 更新画笔颜色（圈选模式使用不同颜色）
            canvas.freeDrawingBrush.color = mode === 'brush' ? brushColor : '#ff0000';
            
            // 更新按钮状态
            brushBtn.classList.toggle('active', mode === 'brush');
            selectionBtn.classList.toggle('active', mode === 'selection');
            
            // 更新状态显示
            statusDisplay.textContent = `当前模式: ${mode === 'brush' ? '画笔' : '圈选'}`;
            
            // 重置填充计时器
            if (fillTimer) {
                clearTimeout(fillTimer);
                fillTimer = null;
            }
            
            // 更新鼠标指针样式
            canvas.defaultCursor = mode === 'brush' ? 'crosshair' : 'move';
            
            // 显示提示
            showNotification(`已切换到${mode === 'brush' ? '画笔' : '圈选'}模式`);
        }
        
        // 鼠标按下事件 - 支持两种模式绘制
        canvas.on('mouse:down', function(options) {
            if (!canvas.isDrawingMode) return;
            
            isDrawing = true;
            const pointer = canvas.getPointer(options.e);
            
            // 创建新路径，并标记是画笔模式还是圈选模式创建的
            currentPath = new fabric.Path(`M ${pointer.x} ${pointer.y}`, {
                stroke: canvas.freeDrawingBrush.color,
                strokeWidth: brushSize,
                fill: '',
                strokeLineCap: 'round',
                strokeLineJoin: 'round',
                selectable: false,
                originMode: currentMode // 标记创建模式
            });
            
            canvas.add(currentPath);
        });
        
        // 鼠标移动事件 - 绘制路径并处理自动填充
        canvas.on('mouse:move', function(options) {
            if (!isDrawing || !currentPath) return;
            
            const pointer = canvas.getPointer(options.e);
            const pathData = currentPath.path;
            
            // 添加新的线段点
            pathData.push(['L', pointer.x, pointer.y]);
            currentPath.set('path', pathData);
            canvas.renderAll();
            
            // 圈选模式下处理自动填充逻辑
            if (currentMode === 'selection') {
                isMouseMoving = true;
                
                // 清除之前的计时器
                if (fillTimer) {
                    clearTimeout(fillTimer);
                }
                
                // 设置新的计时器
                fillTimer = setTimeout(function() {
                    if (!isMouseMoving) { // 确认鼠标已停止移动
                        attemptAutoFill();
                    }
                }, fillDelay);
            }
        });
        
        // 鼠标释放事件
        canvas.on('mouse:up', function() {
            isDrawing = false;
            isMouseMoving = false;
            currentPath = null;
            
            // 圈选模式下鼠标释放时尝试填充
            if (currentMode === 'selection') {
                // 如果计时器仍在运行，立即尝试填充
                if (fillTimer) {
                    clearTimeout(fillTimer);
                    attemptAutoFill();
                }
            }
        });
        
        // 尝试自动填充
        function attemptAutoFill() {
            if (canvas.getObjects().length === 0) {
                showNotification('请先绘制封闭图形。', 'warning');
                return;
            }
            
            // 获取最后绘制的路径
            const lastPath = canvas.getObjects().slice(-1)[0];
            
            // 确保是路径对象
            if (lastPath && lastPath.type === 'path') {
                const pathCoords = lastPath.path;
                
                // 至少需要4个点（起点+3个其他点）
                if (pathCoords.length >= 4) {
                    const firstPoint = { x: pathCoords[0][1], y: pathCoords[0][2] };
                    const lastPoint = { x: pathCoords[pathCoords.length - 1][1], y: pathCoords[pathCoords.length - 1][2] };
                    
                    // 计算路径总长度
                    let totalLength = 0;
                    for (let i = 1; i < pathCoords.length; i++) {
                        const dx = pathCoords[i][1] - pathCoords[i-1][1];
                        const dy = pathCoords[i][2] - pathCoords[i-1][2];
                        totalLength += Math.sqrt(dx * dx + dy * dy);
                    }
                    
                    // 计算起点和终点的距离
                    const startEndDistance = Math.sqrt(
                        Math.pow(lastPoint.x - firstPoint.x, 2) + 
                        Math.pow(lastPoint.y - firstPoint.y, 2)
                    );
                    
                    // 计算"开放度"：起点终点距离占总路径长度的百分比
                    const openness = (startEndDistance / totalLength) * 100;
                    
                    // 如果开放度小于阈值，认为可以自动闭合
                    if (openness < closureThreshold) {
                        // 获取路径边界框
                        const bounds = lastPath.getBoundingRect();
                        
                        // 创建填充多边形，包含自动闭合线段
                        const points = [];
                        for (let i = 0; i < pathCoords.length; i++) {
                            const p = new fabric.Point(pathCoords[i][1], pathCoords[i][2]);
                            const transformedPoint = fabric.util.transformPoint(
                                p,
                                lastPath.calcTransformMatrix()
                            );
                            points.push({ 
                                x: transformedPoint.x - bounds.left, 
                                y: transformedPoint.y - bounds.top 
                            });
                        }
                        
                        // 添加从终点到起点的闭合线段
                        const startPointTransformed = fabric.util.transformPoint(
                            new fabric.Point(firstPoint.x, firstPoint.y),
                            lastPath.calcTransformMatrix()
                        );
                        points.push({ 
                            x: startPointTransformed.x - bounds.left, 
                            y: startPointTransformed.y - bounds.top 
                        });
                        
                        // 创建多边形并设置与原路径相同的位置和变换
                        const polygon = new fabric.Polygon(points, {
                            left: bounds.left,
                            top: bounds.top,
                            fill: fillColor,
                            opacity: 0.5,
                            stroke: null,
                            selectable: false,
                            angle: lastPath.angle,
                            scaleX: lastPath.scaleX,
                            scaleY: lastPath.scaleY,
                            originMode: 'selection' // 标记为圈选模式创建的对象
                        });
                        
                        canvas.add(polygon);
                        canvas.renderAll();
                        showNotification(`区域已自动填充 (开放度: ${openness.toFixed(1)}%)`);
                    } else {
                        showNotification(`图形开放度太大 (${openness.toFixed(1)}%)，请继续绘制或调整闭合阈值。`, 'warning');
                    }
                } else {
                    showNotification('图形未闭合，请继续绘制或重新开始。', 'warning');
                }
            } else {
                showNotification('请先绘制封闭图形。', 'warning');
            }
        }
        
        // 导出黑白图片
        function exportBlackAndWhiteImage() {
            if (canvas.getObjects().length === 0) {
                showNotification('画布为空，没有内容可导出。', 'warning');
                return;
            }
            
            // 保存当前画布状态
            const originalObjects = canvas.getObjects().slice();
            const originalBackgroundColor = canvas.backgroundColor;
            
            // 创建黑白版本的画布
            canvas.clear();
            
            // 处理所有对象，包括嵌套对象
            originalObjects.forEach(obj => {
                processObjectForExport(obj);
            });
            
            // 设置背景为黑色
            canvas.backgroundColor = '#000000';
            canvas.renderAll();
            
            // 生成图片URL
            const dataURL = canvas.toDataURL({
                format: 'png',
                quality: 1
            });
            
            // 显示预览
            exportImage.src = dataURL;
            downloadLink.href = dataURL;
            downloadLink.style.display = 'inline-block';
            
            // 恢复原始画布状态
            canvas.clear();
            originalObjects.forEach(obj => canvas.add(obj));
            canvas.backgroundColor = originalBackgroundColor;
            canvas.renderAll();
            
            showNotification('黑白图片已生成，请查看预览。');
        }
        
        // 递归处理对象及其子对象
        function processObjectForExport(obj) {
            // 使用toObject方法获取对象配置，然后创建新对象
            const objConfig = obj.toObject();
            let newObj = null;
            
            // 根据对象类型创建新实例
            switch(obj.type) {
                case 'path':
                    newObj = new fabric.Path(objConfig.path, objConfig);
                    break;
                case 'circle':
                    newObj = new fabric.Circle(objConfig);
                    break;
                case 'rect':
                    newObj = new fabric.Rect(objConfig);
                    break;
                case 'polygon':
                    newObj = new fabric.Polygon(objConfig.points, objConfig);
                    break;
                case 'line':
                    newObj = new fabric.Line([objConfig.x1, objConfig.y1, objConfig.x2, objConfig.y2], objConfig);
                    break;
                case 'group':
                    // 创建空组
                    newObj = new fabric.Group([], objConfig);
                    
                    // 递归处理子对象
                    obj.getObjects().forEach(child => {
                        const newChild = processObjectForExport(child);
                        newObj.addWithUpdate(newChild);
                    });
                    break;
                default:
                    // 尝试使用通用方式创建对象
                    newObj = new fabric[obj.type.charAt(0).toUpperCase() + obj.type.slice(1)](objConfig);
            }
            
            // 如果成功创建了新对象，设置黑白样式
            if (newObj) {
                // 根据对象来源模式设置不同的黑白样式
                if (obj.originMode === 'brush') {
                    // 画笔模式创建的对象：只保留描边，填充设为透明
                    newObj.set('stroke', '#ffffff');
                    newObj.set('fill', 'transparent');
                } else if (obj.originMode === 'selection') {
                    // 圈选模式创建的对象：保留填充，描边设为透明
                    newObj.set('fill', '#ffffff');
                    newObj.set('stroke', 'transparent');
                } else {
                    // 其他对象：填充和描边都设为白色
                    if (newObj.fill !== undefined) {
                        newObj.set('fill', '#ffffff');
                    }
                    
                    if (newObj.stroke !== undefined) {
                        newObj.set('stroke', '#ffffff');
                    }
                }
                
                // 添加到画布
                canvas.add(newObj);
            } else {
                console.warn('无法克隆对象:', obj.type);
            }
            
            return newObj;
        }
        
        // 显示通知
        function showNotification(message, type = 'info') {
            // 创建通知元素
            const notification = document.createElement('div');
            notification.className = `notification ${type}`;
            notification.textContent = message;
            
            // 设置样式
            notification.style.position = 'fixed';
            notification.style.bottom = '20px';
            notification.style.left = '50%';
            notification.style.transform = 'translateX(-50%)';
            notification.style.padding = '10px 20px';
            notification.style.borderRadius = '4px';
            notification.style.color = 'white';
            notification.style.backgroundColor = type === 'warning' ? '#ff9800' : '#4CAF50';
            notification.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
            notification.style.zIndex = '1000';
            notification.style.opacity = '0';
            notification.style.transition = 'opacity 0.3s';
            
            // 添加到页面
            document.body.appendChild(notification);
            
            // 显示通知
            setTimeout(() => {
                notification.style.opacity = '1';
            }, 10);
            
            // 3秒后隐藏通知
            setTimeout(() => {
                notification.style.opacity = '0';
                setTimeout(() => {
                    document.body.removeChild(notification);
                }, 300);
            }, 3000);
        }
        
        // 默认使用画笔模式
        switchMode('brush');
    </script>
</body>
</html>